package com.faxsun.web.endpoint;

import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import javax.annotation.PostConstruct;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import mblog.web.formatter.JsonUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.faxsun.core.persist.entity.LivePO;
import com.faxsun.core.service.CommentService;
import com.faxsun.core.service.LiveAdminService;
import com.faxsun.core.service.LiveMsgService;
import com.faxsun.core.service.WechatService;
import com.faxsun.web.endpoint.CommentAsyncTask.CommentTaskItem;
import com.faxsun.web.endpoint.entity.LiveCommentJSON;

/**
 * Use comet (client-side long-polling and server-side "blocking" servlet feature)
 * to implement comment REST service. NOT use WebSocket concerning compatibility.
 * <p>
 * Typical client-side using jquery and thymeleaf template like this:
 * <pre>
 * &lt;!--server pass liveId on page loading or from Javascript code URL parse--&gt;
 * &lt;div id="liveComments" liveId="${liveId}" startId="0" &gt; 
 *    &lt;!--comments display here--&gt;
 *    &lt;div id="${comment.id}" class="${comment.author.id}" th:test="${comment.content}"&gt;
 *    &lt;/div&gt;
 * &lt;/div&gt; 
 *   
 * &lt;script th:inline="javascript"&gt;  
 *   function long_polling() {
 *　         $.ajax({
 *           type : "GET",
 *           url : "@{/api/v1/liveComment}",
 *           data : JSON.stringify({
 *               liveId : $("#liveComments").attr("liveId"),
 *               startId : $("#liveComments").attr("startId")
 *           }),
 *           contentType : "application/json; charset=utf-8",
 *           dataType : "json",
 *           success : function(comments_blacklist) {
 *               if(comments_blacklist){
 *                  if(comments_blacklist.comments && comments_blacklist.comments.length > 0){ *                  
 *                    // update startId on success return
 *                    var latestComment = comments[0];               
 *                    $("#liveComments").attr("startId", latestComment.id); 
 *                    
 *                    // display returned comments on page
 *                    // ...                  
 *                  }
 *                  if(comments_blacklist.blacklist && comments_blacklist.blacklist.length > 0){
 *                    // for each comment whose author id in blacklist, remove it from display
 *                    // ...
 *                  }
 *                  long_polling();       
 *               }
 *           },
 *           error : function(xhr, error) {
 *               //error handling here
 *           }
 *       });     
 *   }
 *   
 *   $(document).ready(function() {
 *       long_polling();
 *   });
 * &lt;/script&gt;
 *  
 * </pre>
 * 
 * @author leima
 *
 */
@Controller
@RequestMapping("/api/v1")
public class LiveCommentEndpoint {

    /** task timeout, default 30 seconds*/
    public static final long TIMEOUT = 30000;
    
    /** worker pool size, default 1*/
    public static final int POOL_SIZE = 1;
    
    /** page size default 30*/
    public static final int PAGE_SIZE = 30;
    
    @Value("${live.comment.timeout}")
    private String timeoutStr = null;
    
    @Value("${live.comment.worker}")
    private String poolSizeStr = null;   
    
    private int poolsize = POOL_SIZE;    
    public static long timeoutVal = TIMEOUT; 

    private static final Logger LOG = LoggerFactory.getLogger(LiveCommentEndpoint.class);
    
    /** queue for {@link CommentTaskItem}*/
    public static LinkedBlockingQueue<CommentTaskItem> asyncItemList = new LinkedBlockingQueue<CommentTaskItem>(Integer.MAX_VALUE);  
    
    public static ExecutorService workers = null;

    @Autowired
    CommentService commentService;

    @Autowired
    WechatService wechatService;

    @Autowired
    LiveAdminService liveAdminService;
    
    @Autowired
    LiveMsgService liveMsgService;
    
    public static volatile boolean shutdown = false;
    
    @PostConstruct
    public void startTask(){ 
        if(timeoutStr != null){
           timeoutVal = Long.parseLong(timeoutStr);
        }
        
        if(poolSizeStr != null){
           poolsize = Integer.parseInt(poolSizeStr);
        }

        workers = Executors.newFixedThreadPool(poolsize, new MyThreadFactory());
        CommentAsyncTask.init(poolsize);
        
        for(int i = 0;i < poolsize;i++){
            CommentAsyncTask task = new CommentAsyncTask(commentService, liveAdminService, liveMsgService);        
            workers.submit(task);
        }
        
        Thread t = new Thread(new ShutdownWorkers());
        t.setName("LiveCommentPoolSentinel");
        t.start();        
    }

    @RequestMapping(value = "/liveComment", method = RequestMethod.GET, 
                    consumes = "application/json; charset=UTF-8", 
                    produces = "application/json; charset=UTF-8")    
    public void getRecentComments(HttpServletRequest request, HttpServletResponse response,
            @RequestParam(value="liveId", required = true) Long liveId,
            @RequestParam(value="startId", required = false) Long startId,
            @RequestParam(value="endId", required = false) Long endId,
            @RequestParam(value="size", required = true) Integer size) {
        
        LivePO live = wechatService.findEventById(liveId);
        
        if (live == null) {
            try {
                request.getRequestDispatcher("/api/v1/liveComment/sorry_not_found_" + liveId).forward(request, response);
            } catch (ServletException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return;
        }
        
        long sid = -1;
        if(startId != null){  
           sid = startId > 0 ? startId : -1;           
        }
        
        long eid = -1;
        if(endId != null || sid <= 0){
           eid = (endId == null) ? (Long.MAX_VALUE) : (endId <= sid? Long.MAX_VALUE : endId);
        }
        
        request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
        AsyncContext asyncContext = request.startAsync();
        asyncContext.setTimeout(0);        

        long liveStart = live.getStartTime().getTime();
        long liveEnd = live.getEndTime().getTime();
        long now = System.currentTimeMillis(); 
        
        CommentTaskItem item = new CommentTaskItem(asyncContext, now, liveId, sid, eid, size);

        // respond immediately if not in live or request for past comments
        if((now < liveStart || now > liveEnd) || (eid > 0 && sid <= 0)){
            LiveCommentJSON data = item.getData(commentService, liveAdminService, liveMsgService);      
            LOG.info("trigger to send message:"+JsonUtils.toJson(data));
            item.completeRequest(data, false);
        }else{            
            try {
                asyncItemList.put(item);
            } catch (InterruptedException e) {
                LOG.warn("fail to enqueue request for live comment, abandon response", e);
            }            
        }        
    }
    
    class MyThreadFactory implements ThreadFactory{

        private AtomicInteger id = new AtomicInteger(0);
        
        private ThreadFactory defaultTf; 
        
        MyThreadFactory(){
            defaultTf = Executors.defaultThreadFactory();    
        }
        
        @Override
        public Thread newThread(Runnable r) {
            Thread t = defaultTf.newThread(r);
            t.setName("LiveCommentFetchThread" + id.incrementAndGet());
            return t;
        }
        
    }
    
    class ShutdownWorkers implements Runnable{

        @Override
        public void run() {
            try {
                CommentAsyncTask.cdl.await();
                workers.shutdownNow();
                workers.awaitTermination(timeoutVal, TimeUnit.MILLISECONDS);
                return;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
    }
}
